/**
 * Contains The Server, which listen for Clients attempting to communicate an Order
 */
package server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import wfta.common.Item;
import wfta.common.Order;
import wfta.common.Section;



/**
 * @author Michele Bortolato
 * Accept Requests from remote clients, wich attempt to comunicate an Order
 */
public class Server {

	/**
	 * Port 
	 */
	private short _PORT=4444;

	/**
	 *Hash Table that associate every item to its ID 
	 */
	private HashMap<Short, Item> items=new HashMap<Short, Item>();

	/**
	 * Read the repository of items, instantiate the has table and run the client listener
	 */
	public void Init(){
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		try {
			//Read items.xml
			db = dbf.newDocumentBuilder();
			Document doc = db.parse("items.xml");
			doc.getDocumentElement().normalize();
			Element root=doc.getDocumentElement();
			//System.out.println(root.getNodeName());
			NodeList nl= root.getElementsByTagName("category");

			//numCategory=nl.getLength();

			//scan file adding items to hasmaps
			//Don't care here for mutual exclusion for items because
			// now there is only an user for items
			for(int i=0;i<nl.getLength();i++){
				Element el=(Element)nl.item(i);
				String cat=el.getAttribute("name");

				NodeList nll=el.getElementsByTagName("item");
				for(int j=0;j<nll.getLength();j++){
					//get every item
					Element ell=(Element)nll.item(j);
					items.put(new Short(ell.getAttribute("id")),
							new Item(ell.getAttribute("name"),
									ell.getTextContent(),
									cat,
									new Short(ell.getAttribute("id")),
									new Double(ell.getAttribute("price")),
									new Short(ell.getAttribute("qty"))));
				}
			}
			//System.out.println(">>"+items.get(((short) 8)));
			//
			new MainThread().run();
			//Connect
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/** Private class for accepting new clients
	 * @author Michele Bortolato
	 *
	 */
	private class MainThread implements Runnable{

		/**
		 * Constructor
		 */
		MainThread(){}

		/** Listen for new Clients
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			//Waiting for new clients
			ServerSocket srv;
			try {
				srv = new ServerSocket(_PORT);
				while(true){
					(new CustomerServer(srv.accept())).start();		
				}
			} catch (IOException e1) {
				e1.printStackTrace();
				System.exit(0);
			}
		}

	};

	/** Permit mutial exclusion for writing the availability of an
	 *  item on the menu
	 * @param ID ID of item to decrease
	 * @param qty quantity to decrease
	 */
	private void decreaseItem(short ID, int qty){
		synchronized (items){
			items.get(ID).setAvail(items.get(ID).getAvail()-qty);
		}
	}

	/**Remove race condition for reading availability of an item on the menu
	 * @param ID ID of the item
	 * @return Availabiliy of item ID
	 */
	private short getAvail(short ID){
		synchronized(items){
			//System.out.println("Avail:"+ID);
			Item t=items.get(ID);
			//System.out.println(t);
			return t.getAvail();
		}
	}

	/**Return an item forom menu avoiding mutual exclusion
	 * @param ID ID of the item
	 * @return The ID item
	 */
	private Item getItem(short ID){
		synchronized(items){
			return (Item)items.get(ID);
		}
	}

	/**Useless method for read name of an item avoiding mutual exclusion
	 * @param ID ID of the item
	 * @return Name of item ID
	 */
	private String getItemName(short ID){
		synchronized(items){
			return items.get(ID).getName();
		}
	}
	/** Private class for manage a client request
	 * @author Michele Bortolato
	 *
	 */
	private class CustomerServer extends Thread{

		/**
		 * Output stream for the Client
		 */
		ObjectOutputStream os;

		/**
		 * Input Stream for the Client
		 */
		ObjectInputStream is;

		/**
		 * Client Socket
		 */
		private Socket clSocket;

		/**Constructor
		 * @param accept Client-Server Socket
		 */
		CustomerServer(Socket accept) {
			// 
			clSocket=accept;
			System.out.println("New Connection");
			//Initialize inner fields
			try {
				os=new ObjectOutputStream(accept.getOutputStream());
				is=new ObjectInputStream(accept.getInputStream());
			} catch (IOException e) {
				// 
				e.printStackTrace();
				//System.exit(0);
			}
		}

		/** Manage a Client Request, send the menu, wait for an ordination, elaborate the time to serve
		 * and call the client when finished
		 * @see java.lang.Runnable#run()
		 */
		@SuppressWarnings("unchecked")
		public void run() {
			// Send Menu to Client
			Vector< Section> menu=new Vector< Section>();

			try {
				//
				Set<Short> idSet=items.keySet();
				Iterator<Short> itIdSet=idSet.iterator();

				while(itIdSet.hasNext()){
					//Getting Item
					Short id= (Short) itIdSet.next();
					Item tmpItem=getItem(id);
					//System.out.println(tmpItem.getName());

					boolean add=false;
					for(Section i : menu){
						if(i.getName().equals(tmpItem.getCategory())){
							//Add in this Section
							i.add(tmpItem);
							add=true;
							//menu.add(e)
						}
					}
					if(!add){
						//New Section for this Item
						Section n=new Section(tmpItem.getCategory());
						n.add(tmpItem);
						menu.add(n);
					}
				}
				//Send menu
				os.writeObject(menu);
				System.out.println("\tSent Menu");
				//Wait for ordination
				//Or catch the interruption of service
				String message="$";
				Vector<Order> or = null;
				boolean exit =false;
				
				while(!exit){
					or=null;
					or=(Vector<Order>)is.readObject();
					System.out.println("\tOrdination Received");
					if(or.size()==0){
						//Empty order
						;
					}
					synchronized (Server.this.items){
						for(Order it : or){
							//Check if some order exceed the availability of
							// the restaurant
							short qt=(getAvail(it.getID()));
							if(qt!=-1&&qt<it.getQuantity()){
								//Cannot supply the order
								message+=getItemName(it.getID())+
								" not sufficiently Available\n";
							}

						}
						if(message.equals("$")){
							message="%Your Order Has Been Approved";
							os.writeObject(message);
							exit=true;
						} else {
							os.writeObject(message);
							message="$";
							System.out.println("Order Rejected");
						}
					}
				}
				int waitTime=0;
				//os.writeObject(message);
				System.out.println("\tOrder Accepted");
				if(message.equals("%Your Order Has Been Approved")){
					waitTime+=3000;
					//Order could be supplied
					//Update quantities of items
					for(Order it : or){
						Short qt=it.getQuantity();
						Short itID=it.getID();
						if(getAvail(itID)!=-1)
							decreaseItem(itID, qt);
						//items.get(itID).setAvail(items.get(itID).getAvail() - qt);

						//A banal method to calculate the time to response
						//A second for each item ordered
						waitTime+=qt*1000;
					}
				} else {
					//Not an Order
				}
				os.writeObject("%Estimated Time:"+waitTime/1000+" Seconds.");
				Thread.sleep(waitTime);
				System.out.println("Millisec:"+waitTime);
				os.writeObject("%You could pass for retire your lunch");

				os.writeObject("&Closing_connection");
				clSocket.close();
				System.out.println("End Connection");

				return;

			} catch (IOException e) {
				e.printStackTrace();
				//System.exit(0)
				;
			} catch (ClassNotFoundException e) {
				// 
				e.printStackTrace();
				//System.exit(0);
			} catch (InterruptedException e) {
				e.printStackTrace();
				//System.exit(0);
			} catch (NullPointerException e){
				e.printStackTrace();
			}
			System.out.println("Broken Communication By The User");
		}

	};
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Server().Init();

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}
